﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using Dimebrain.TweetSharp.Core.Extensions;

namespace Dimebrain.TweetSharp.Core.Web
{
    public class WebQueryClient : IWebQueryClient
    {
        public WebResponse Response { get; private set; }
        public WebRequest Request { get; private set; }
        public ICredentials Credentials { get; set; }
        public bool UseCompression { get; set; }
        public string ProxyValue { get; set; }

        private readonly IDictionary<string, string> _headers;
        public WebParameterCollection Parameters { get; private set; }
        public string UserAgent { get; private set; }
        public bool UseTransparentProxy { get; private set; }

        public WebQueryClient(IDictionary<string, string> headers, WebParameterCollection parameters, string userAgent, bool useTransparentProxy)
        {
            _headers = headers;
            Parameters = parameters;
            UserAgent = userAgent;
            UseTransparentProxy = useTransparentProxy;
        }

        public WebRequest GetWebRequestShim(Uri address)
        {
            var request = (HttpWebRequest) WebRequest.Create(address);

            // You can't set headers in a GET request in Silverlight,
            // so override to POST in all cases and use the 'X-Twitter-Method'
            // header value to let the proxy know it should be a GET.
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            if (UseTransparentProxy)
            {
                request.Headers["X-Twitter-Method"] = "GET";
                if (_headers != null)
                {
                    foreach (var header in _headers)
                    {
                        request.Headers[header.Key] = header.Value;
                    }
                }

                if (UseCompression)
                {
                    request.Headers["X-Twitter-Accept"] = "gzip,deflate";
                }

                if (Credentials != null)
                {
                    var credentials = (NetworkCredential)Credentials;
                    request.Headers["X-Twitter-Auth"] = credentials.ToAuthorizationHeader();
                }

                if (!UserAgent.IsNullOrBlank())
                {
                    request.Headers["X-Twitter-Agent"] = UserAgent;
                }

                if (Parameters != null)
                {
                    var hasParameters = address.Query.Contains("?");
                    foreach (var parameter in Parameters)
                    {
                        address.Query.Then(hasParameters ? "&" : "?");
                        address.Query.Then("{0}={1}".FormatWith(parameter.Name, parameter.Value));
                        hasParameters = true;
                    }
                }
            }

            Request = request;
            return request;
        }

        public WebResponse GetWebResponseShim(WebRequest request, IAsyncResult result)
        {
            try
            {
                var response = request.EndGetResponse(result);
                Response = response;
                return response;
            }
            catch (WebException ex)
            {
                return HandleWebException(ex);
            }
        }

        public event EventHandler<OpenReadCompletedEventArgs> OpenReadCompleted;

        public void OnOpenReadCompleted(Stream stream, object state)
        {
            if (OpenReadCompleted == null)
            {
                return;
            }
            var args = new OpenReadCompletedEventArgs(stream, state);
            OpenReadCompleted(this, args);
        }

        public void OpenReadAsync(Uri uri)
        {
            var request = (HttpWebRequest)GetWebRequestShim(uri);
            
            // A stream is always used because POST is required
            request.BeginGetRequestStream(RequestStreamCallback, request);
        }

        public void OpenReadAsync(Uri uri, object state)
        {
            var request = GetWebRequestShim(uri);
            var pair = new Pair<WebRequest, object>
                           {
                               First = request,
                               Second = state
                           };

            // A stream is always used because POST is required
            request.BeginGetRequestStream(RequestStreamCallback, pair);
        }

        private void RequestStreamCallback(IAsyncResult ar)
        {
            WebRequest request;
            if(ar.AsyncState is WebRequest)
            {
                request = (HttpWebRequest)ar.AsyncState;
            }
            else
            {
                var pair = (Pair<WebRequest, object>) ar.AsyncState;
                request = pair.First;
            }
            
            var url = request.RequestUri.ToString();
            var content = Encoding.UTF8.GetBytes(url);

            using (var stream = request.EndGetRequestStream(ar))
            {
                stream.Write(content, 0, content.Length);
                stream.Close();

                request.BeginGetResponse(ResponseStreamCallback, ar.AsyncState);
            }
        }

        private void ResponseStreamCallback(IAsyncResult ar)
        {
            HttpWebRequest request;
            if (ar.AsyncState is WebRequest)
            {
                request = (HttpWebRequest)ar.AsyncState;
            }
            else
            {
                var pair = (Pair<WebRequest, object>)ar.AsyncState;
                request = (HttpWebRequest)pair.First;
            }

            try
            {
                var response = request.EndGetResponse(ar);
                var stream = response.GetResponseStream();

                Response = response;
                var state = ar.AsyncState is Pair<WebRequest, object>
                                ? ((Pair<WebRequest, object>) ar.AsyncState).
                                      Second
                                : null;

                OnOpenReadCompleted(stream, state);
            }
            catch(WebException ex)
            {
                Response = HandleWebException(ex);
            }
        }

        private static WebResponse HandleWebException(WebException ex)
        {
            if (ex.Response != null && ex.Response is HttpWebResponse)
            {
                return ex.Response;
            }

            // not the droids we're looking for
            throw ex;
        }
    }
}
